﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using NLog;
using System.Security;

namespace ConsoleService
{
    public class ActionExecuter
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private Action _action;
        private Instance _instance;
        private Thread _thread;

        public DateTime LastUpdate { get; set; }
        public bool HasFinished
        {
            get
            {
                return this._action.is_executed;
            }
        }

        delegate void ActionExecutionEndedDelegate(bool success, string status);
        private ActionExecutionEndedDelegate _executionEnded;

        private class ActionData
        {
            public ActionExecutionEndedDelegate EndedEvent { get; set; }
            public Instance Instance { get; set; }
            public Action Action { get; set; }
        }

        public ActionExecuter(Instance instance, Action action)
        {
            this._action = action;
            this._instance = instance;
            LastUpdate = DateTime.Now;
        }

        public void Abort()
        {
            if (this._action != null)
            {
                this._action.is_processing = false;
            }
            if (this._thread != null)
            {
                _thread.Abort();
            }
        }

        public void ActionExecutionEnded(bool success, string status)
        {
            logger.Debug("Action {0} Result: {1} / {2}", this._action.action_id, success, status);
            this._action.is_executed = true;
            this._action.is_processing = false;
            this._action.is_success = success;
            this._action.status = status;
            this._action.end_datetime = DateTime.Now;
        }

        public bool Start()
        {
            var started = false;
            LastUpdate = DateTime.Now;
            this._executionEnded += new ActionExecutionEndedDelegate(ActionExecutionEnded);
            logger.Debug("Action {0} Starting", this._action.action_id);

            switch(this._action.action.ToLowerInvariant())
            {
                case "stop":
                    if (this._instance.server.is_stopped && !this._instance.server.is_starting)
                    {
                        this._instance.server.is_starting = false;
                        this._instance.server.is_stopping = false;
                        this._instance.server.is_updating = false;
                        this._instance.server.is_started = false;
                        this._instance.server.is_stopped = true;
                        started = false;
                        logger.Debug("Action {0}: Already Stopped", this._action.action_id);
                    }
                    else
                    {
                        _thread = new Thread(new ParameterizedThreadStart(StopServer));
                        var data = new ActionData()
                        {
                            Instance = _instance,
                            Action = _action,
                            EndedEvent = new ActionExecutionEndedDelegate(ActionExecutionEnded)
                        };
                        _thread.Start(data);
                        this._action.is_processing = true;
                        started = true;
                    }
                    break;
                case "update":
                    if (this._instance.server.is_stopped && this._instance.server.is_updating)
                    {
                        this._instance.server.is_starting = false;
                        this._instance.server.is_stopping = false;
                        this._instance.server.is_updating = true;
                        this._instance.server.is_started = false;
                        this._instance.server.is_stopped = true;
                        started = false;
                        logger.Debug("Action {0}: Already Updating", this._action.action_id);
                    }
                    else
                    {
                        _thread = new Thread(new ParameterizedThreadStart(UpdateServer));
                        var data = new ActionData()
                        {
                            Instance = _instance,
                            Action = _action,
                            EndedEvent = new ActionExecutionEndedDelegate(ActionExecutionEnded)
                        };
                        _thread.Start(data);
                        this._action.is_processing = true;
                        started = true;
                    }
                    break;
                default:
                    if (this._instance.server.is_started)
                    {
                        this._instance.server.is_starting = false;
                        this._instance.server.is_stopping = false;
                        this._instance.server.is_updating = false;
                        this._instance.server.is_started = true;
                        this._instance.server.is_stopped = false;
                        started = false;
                        logger.Debug("Action {0}: Already Started", this._action.action_id);
                    }
                    else
                    {
                        _thread = new Thread(new ParameterizedThreadStart(StartServer));
                        var data = new ActionData()
                        {
                            Instance = _instance,
                            Action = _action,
                            EndedEvent = new ActionExecutionEndedDelegate(ActionExecutionEnded)
                        };
                        _thread.Start(data);
                        this._action.is_processing = true;
                        started = true;
                    }
                    break;
            }
            return started;
        }

        private void StartServer(object data)
        {
            ActionData actionData = data as ActionData;
            if (actionData != null)
            {
                try
                {
                    var success = false;
                    var status = "";
                    var instance = actionData.Instance;
                    var action = actionData.Action;
                    logger.Debug("Action {0}: Starting the server {1}", action.action_id, instance.server.server_id);

                    var pid = instance.GetPID();
                    if (pid <= 0)
                    {
                        var serverPath = System.IO.Path.Combine(instance.server.path, instance.server.executable);
                        var commandline = System.Configuration.ConfigurationManager.AppSettings.Get("DefaultCommandLine");
                        var logfilepath = System.IO.Path.Combine(instance.server.config_path, System.Configuration.ConfigurationManager.AppSettings.Get("LogFileName").Replace("{0}", "*").Replace("{1}", "*").Replace("{2}", "*").Replace("{3}", "*").Replace("{4}", "*"));
                        var runAsAdmin = Boolean.Parse(System.Configuration.ConfigurationManager.AppSettings.Get("RunAsAdmin"));
                        var username = "";
                        var password = "";
                        if (System.Configuration.ConfigurationManager.AppSettings.AllKeys.Contains("Username"))
                        {
                            username = System.Configuration.ConfigurationManager.AppSettings.Get("Username");
                            password = System.Configuration.ConfigurationManager.AppSettings.Get("Password");
                        }
                        if (instance.server.templates != null && instance.server.templates.Count > 0)
                        {
                            try
                            {
                                for (var i = 0; i < instance.server.templates.Count; i++ )
                                {
                                    if (instance.server.templates.Count > i && instance.server.templates[i] != null)
                                    {
                                        var template = instance.server.templates[i];
                                        try
                                        {
                                            var templatePath = System.IO.Path.Combine(instance.server.template_path, template.destination);
                                            var destinationPath = System.IO.Path.Combine(instance.server.config_path, template.destination);
                                            if (!string.IsNullOrWhiteSpace(template.path_override))
                                            {
                                                destinationPath = System.IO.Path.Combine(template.path_override, template.destination);
                                            }
                                            if (System.IO.File.Exists(templatePath))
                                            {
                                                if (template.is_commandline)
                                                {
                                                    logger.Trace("Using commandline template {0}", templatePath);
                                                    commandline = System.IO.File.ReadAllText(templatePath);
                                                }
                                                else
                                                {
                                                    logger.Trace("Copying template {0}", templatePath);
                                                    System.IO.File.Copy(templatePath, destinationPath, true);
                                                }
                                            }
                                            else
                                            {
                                                logger.Error("Missing template: {0}", templatePath);
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            logger.Error(string.Format("Template Creation: {0}", template.template_id), ex);
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                logger.Error(string.Format("Template Processing for server: {0}", instance.server.server_id), ex);
                            }

                        }
                        try
                        {
                            var files = System.IO.Directory.GetFiles(System.IO.Path.GetDirectoryName(logfilepath), System.IO.Path.GetFileName(logfilepath));
                            if (files != null && files.Length > 0)
                            {
                                foreach (var file in files)
                                {
                                    var tries = 5;
                                    while (tries > 0)
                                    {
                                        try
                                        {
                                            var filename = System.IO.Path.GetFileNameWithoutExtension(file);
                                            var newname = System.IO.Path.Combine(instance.server.config_path, "archive");
                                            if (!System.IO.Directory.Exists(newname))
                                            {
                                                System.IO.Directory.CreateDirectory(newname);
                                            }
                                            newname = System.IO.Path.Combine(newname, (filename + "." + DateTime.Now.ToString("yyyyMMddHHmmss") + ".arc"));
                                            System.IO.File.Move(file, newname);
                                            tries = 0;
                                        }
                                        catch (Exception ex)
                                        {
                                            logger.Error(string.Format("Failed renaming the log file: {0}", file), ex);
                                            System.Threading.Thread.Sleep(500);
                                            tries--;
                                        }
                                    }
                                };
                                logger.Trace("Moved all ({1}) logfiles for server {0}", instance.server.server_id, files.Length);
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.Error(string.Format("Moving log files for server: {0}", instance.server.server_id), ex);
                        }
                        System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
                        logger.Info("Start Arguments: {0}", commandline);
                        startInfo.Arguments = commandline;
                        startInfo.FileName = serverPath;
                        if (runAsAdmin)
                        {
                            logger.Debug("Using runas for starting");
                            startInfo.Verb = "runas";
                        }
                        if (!string.IsNullOrWhiteSpace(username) && username.Length > 0 && !string.IsNullOrWhiteSpace(password) && password.Length > 0)
                        {
                            logger.Debug("Using username/password for starting");
                            startInfo.UserName = username;
                            var pwd = new SecureString();
                            foreach (var chr in password.ToCharArray())
                            {
                                pwd.AppendChar(chr);
                            }
                            startInfo.Password = pwd;
                        }
                        instance.process = new System.Diagnostics.Process();
                        instance.process.StartInfo = startInfo;
                        if (instance.process.Start())
                        {
                            status = "Server started successfully!";
                            success = true;
                        }
                        else
                        {
                            status = "Failed to start the server!";
                            instance.server.is_started = false;
                            instance.server.is_starting = false;
                            instance.server.is_stopped = true;
                            instance.server.is_stopping = false;
                            instance.server.is_updating = false;
                            success = false;
                        }
                    }
                    else
                    {
                        status = "Server already started";
                        success = true;
                    }

                    if (success)
                    {
                        instance.server.is_started = true;
                        instance.server.is_starting = false;
                        instance.server.is_stopped = false;
                        instance.server.is_stopping = false;
                        instance.server.is_updating = false;
                    }

                    if (actionData.EndedEvent != null)
                    {
                        actionData.EndedEvent.Invoke(success, status);
                    }
                    else
                    {
                        logger.Info("Action {0}: Ending starting the server {1}: {2} / {3}", action.action_id, instance.server.server_id, success, status);
                    }
                }
                catch (Exception ex)
                {
                    logger.Debug(string.Format("Error starting the server {0}", this._instance.server.server_id), ex);
                    if (actionData.EndedEvent != null)
                    {
                        actionData.EndedEvent.Invoke(false, ex.Message);
                    }
                }
            }
        }

        private void StopServer(object data)
        {
            ActionData actionData = data as ActionData;
            if (actionData != null)
            {
                try
                {
                    var success = false;
                    var status = "";
                    var instance = actionData.Instance;
                    var action = actionData.Action;
                    var start = DateTime.Now;
                    logger.Debug("Action {0}: Stopping the server {1}", action.action_id, instance.server.server_id);

                    var pid = instance.GetPID();
                    if (pid > 0 && (instance.process != null && !instance.process.HasExited))
                    {
                        if (instance.process.CloseMainWindow())
                        {
                            logger.Debug("Action {0}: Closed window for server {1}", action.action_id, instance.server.server_id);
                            start = DateTime.Now;
                            while (!instance.process.HasExited && DateTime.Now.Subtract(start).TotalMilliseconds < 3000)
                            {
                                System.Threading.Thread.Sleep(100);
                            }
                            if (instance.process.HasExited)
                            {
                                status = "Server stopped successfully!";
                                success = true;
                            }
                        }
                        if (!success)
                        {
                            logger.Debug("Action {0}: Closing failed for server {1}", action.action_id, instance.server.server_id);
                            start = DateTime.Now;
                            while (!instance.process.HasExited && DateTime.Now.Subtract(start).TotalMilliseconds < 5000)
                            {
                                System.Threading.Thread.Sleep(100);
                            }
                            if (instance.process.HasExited)
                            {
                                status = "Server stopped successfully!";
                                success = true;
                            }
                            else
                            {
                                logger.Debug("Action {0}: Killing server {1}", action.action_id, instance.server.server_id);
                                instance.process.Kill();
                                start = DateTime.Now;
                                while (!instance.process.HasExited && DateTime.Now.Subtract(start).TotalMilliseconds < 5000)
                                {
                                    System.Threading.Thread.Sleep(100);
                                }
                                if (instance.process.HasExited)
                                {
                                    status = "Server killed successfully!";
                                    success = true;
                                }
                                else
                                {
                                    status = "Failed to stop the server!";
                                    success = false;
                                }
                            }
                        }
                    }
                    else
                    {
                        status = "Server already stopped";
                        success = true;
                    }

                    if (success)
                    {
                        instance.server.is_started = false;
                        instance.server.is_starting = false;
                        instance.server.is_stopped = true;
                        instance.server.is_stopping = false;
                        instance.server.is_updating = false;
                        instance.process = null;
                    }
                    else
                    {
                        instance.server.is_started = true;
                        instance.server.is_starting = false;
                        instance.server.is_stopped = false;
                        instance.server.is_stopping = false;
                        instance.server.is_updating = false;
                    }
                    if (actionData.EndedEvent != null)
                    {
                        actionData.EndedEvent.Invoke(success, status);
                    }
                    else
                    {
                        logger.Debug("Action {0}: Ending stopping the server {1}: {2} / {3}", action.action_id, instance.server.server_id, success, status);
                    }
                }
                catch (Exception ex)
                {
                    logger.Debug(string.Format("Error starting the server {0}", this._instance.server.server_id), ex);
                    if (actionData.EndedEvent != null)
                    {
                        actionData.EndedEvent.Invoke(false, ex.Message);
                    }
                }
            }
        }

        private void UpdateServer(object data)
        {
            ActionData actionData = data as ActionData;
            if (actionData != null)
            {
                try
                {
                    var success = false;
                    var status = "";
                    var instance = actionData.Instance;
                    var action = actionData.Action;
                    var updateTimeOut = 60;
                    if (System.Configuration.ConfigurationManager.AppSettings.AllKeys.Contains("UpdateTimeOutMinutes"))
                    {
                        int.TryParse(System.Configuration.ConfigurationManager.AppSettings.Get("UpdateTimeOutMinutes"), out updateTimeOut);
                    };
                    logger.Debug("Action {0}: Updating the server {1}", action.action_id, instance.server.server_id);

                    instance.server.is_updating = true;
                    var stopped = false;
                    var pid = instance.GetPID();
                    if (pid > 0 && (instance.process != null && !instance.process.HasExited))
                    {
                        instance.server.is_stopping = true;
                        if (instance.process.CloseMainWindow())
                        {
                            status = "Server stopped successfully!";
                            stopped = true;
                        }
                        else
                        {
                            System.Threading.Thread.Sleep(5000);
                            if (instance.process.HasExited)
                            {
                                status = "Server stopped successfully!";
                                stopped = true;
                            }
                            else
                            {
                                instance.process.Kill();
                                System.Threading.Thread.Sleep(5000);
                                if (instance.process.HasExited)
                                {
                                    status = "Server killed successfully!";
                                    stopped = true;
                                }
                                else
                                {
                                    status = "Failed to stop the server!";
                                }
                            }
                        }
                    }
                    else
                    {
                        status = "Server already stopped";
                        stopped = true;
                    }
                    if (stopped)
                    {
                        instance.server.is_started = false;
                        instance.server.is_starting = false;
                        instance.server.is_stopped = true;
                        instance.server.is_stopping = false;
                        instance.server.is_updating = true;
                        System.Threading.Thread.Sleep(5000);
                    }
                    if (instance.process == null || instance.process.HasExited)
                    {
                        var batchname = System.IO.Path.Combine(instance.server.path, System.Configuration.ConfigurationManager.AppSettings.Get("UpdateBatchCommand"));
                        if (!string.IsNullOrWhiteSpace(batchname))
                        {
                            batchname = string.Format(batchname, instance.server.name, instance.server.server_id);
                            if (System.IO.File.Exists(batchname))
                            {
                                var updateProcess = new System.Diagnostics.Process();
                                updateProcess.StartInfo = new System.Diagnostics.ProcessStartInfo(batchname);
                                updateProcess.Start();
                                var start = DateTime.Now;
                                while (!updateProcess.HasExited && (DateTime.Now.Subtract(start).TotalMinutes <= updateTimeOut))
                                {
                                    instance.server.is_updating = true;
                                    System.Threading.Thread.Sleep(5000);
                                    this.LastUpdate = DateTime.Now;
                                }
                                if (updateProcess.HasExited)
                                {
                                    status = "Update ended successfully";
                                    success = true;
                                }
                                else
                                {
                                    updateProcess.Kill();
                                    instance.server.is_started = false;
                                    instance.server.is_starting = false;
                                    instance.server.is_stopped = true;
                                    instance.server.is_stopping = false;
                                    instance.server.is_updating = false;
                                    success = false;
                                    status = string.Format("Update process failed after: {0} minutes", DateTime.Now.Subtract(start).TotalMinutes);
                                }
                            }
                            else
                            {
                                instance.server.is_started = false;
                                instance.server.is_starting = false;
                                instance.server.is_stopped = true;
                                instance.server.is_stopping = false;
                                instance.server.is_updating = false;
                                success = false;
                                status = "Could not find the update batch command";
                            }
                        }
                        else
                        {
                            instance.server.is_started = false;
                            instance.server.is_starting = false;
                            instance.server.is_stopped = true;
                            instance.server.is_stopping = false;
                            instance.server.is_updating = false;
                            success = false;
                            status = "Invalid update batch command name";
                        }
                    }
                    else
                    {
                        instance.server.is_started = false;
                        instance.server.is_starting = false;
                        instance.server.is_stopped = false;
                        instance.server.is_stopping = false;
                        instance.server.is_updating = false;
                        success = false;
                        status = "Could not stop the server";
                    }
                    if (success)
                    {
                        instance.server.is_started = false;
                        instance.server.is_starting = false;
                        instance.server.is_stopped = true;
                        instance.server.is_stopping = false;
                        instance.server.is_updating = false;
                    }
                    if (actionData.EndedEvent != null)
                    {
                        actionData.EndedEvent.Invoke(success, status);
                    }
                    else
                    {
                        logger.Debug("Action {0}: Ending update of server {1}: {2} / {3}", action.action_id, instance.server.server_id, success, status);
                    }
                }
                catch (Exception ex)
                {
                    logger.Debug(string.Format("Error starting the server {0}", this._instance.server.server_id), ex);
                    if (actionData.EndedEvent != null)
                    {
                        actionData.EndedEvent.Invoke(false, ex.Message);
                    }
                }
            }
        }

    }
}
